﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Research.Kinect.Nui;
using Blake.NUI.WPF.Utility;
using System.Diagnostics;

namespace Kinductor.Gestures
{
    class BeatPatternRecognizer
    {
        #region Fields

        bool isChanged = false;

        DateTime lastIctus;
        public HandConductingStatus leftHandStatus;
        public HandConductingStatus rightHandStatus;

        Runtime nui;

        #endregion

        #region Properties

        public SkeletonData Skeleton { get; private set; }
        public int PlayerIndex { get; private set; }
        public double BeatsPerMinute { get; private set; }
        public bool IsBeatingActive { get; private set; }

        public bool IsConducting { get; private set; }

        public double BeatConfidence { get; private set; }

        public ConductingStyle Style { get; private set; }

        public double VolumeDelta { get; private set; }
        public bool IsVolumeDeltaActive { get; private set; }

        public double Volume { get; private set; }
        public bool IsVolumeActive { get; private set; }

        #endregion

        #region Events

        #region BeatPatternUpdated

        public event EventHandler BeatPatternUpdated;

        protected void OnBeatPatternUpdated()
        {
            if (BeatPatternUpdated == null)
                return;
            BeatPatternUpdated(this, EventArgs.Empty);
        }

        #endregion

        #endregion

        #region Constructors

        public BeatPatternRecognizer(Runtime kinectRuntime)
        {
            if (kinectRuntime == null)
                throw new ArgumentNullException("kinectRuntime");

            this.nui = kinectRuntime;

            nui.SkeletonFrameReady += new EventHandler<SkeletonFrameReadyEventArgs>(nui_SkeletonFrameReady);
            BeatConfidence = 0;
            BeatsPerMinute = 0;
            Style = ConductingStyle.Normal;
            VolumeDelta = 0;

            leftHandStatus = new HandConductingStatus();
            rightHandStatus = new HandConductingStatus();
        }

        #endregion

        #region Private Methods

        void nui_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e)
        {
            SkeletonFrame allSkeletons = e.SkeletonFrame;

            //get the first tracked skeleton
            Skeleton = (from s in allSkeletons.Skeletons
                        where s.TrackingState == SkeletonTrackingState.Tracked &&
                              s.Joints[JointID.Head].TrackingState == JointTrackingState.Tracked
                        select s).OrderBy((s) => s.Joints[JointID.Head].Position.Z * Math.Abs(s.Joints[JointID.Head].Position.X))
                                 .FirstOrDefault();

            if (Skeleton == null)
            {
                PlayerIndex = -1;
                return;
            }
            PlayerIndex = allSkeletons.Skeletons.ToList().IndexOf(Skeleton) + 1;

            Joint leftHand = Skeleton.Joints[JointID.HandLeft];
            Joint leftWrist = Skeleton.Joints[JointID.WristLeft];
            Joint rightHand = Skeleton.Joints[JointID.HandRight];
            Joint rightWrist = Skeleton.Joints[JointID.WristRight];
            Joint head = Skeleton.Joints[JointID.Head];
            Joint hip = Skeleton.Joints[JointID.HipCenter];
            Joint shoulder = Skeleton.Joints[JointID.ShoulderCenter];

            leftHandStatus.UpdateStatus(leftHand, leftWrist, hip, shoulder);
            rightHandStatus.UpdateStatus(rightHand, rightWrist, hip, shoulder);

            UpdateIsConducting();

            UpdateBeatsPerMinute();

            UpdateVolume();

            OnBeatPatternUpdated();
        }

        private void UpdateVolume()
        {
            //if (rightHandStatus.Phase == HandConductingPhase.Volume)
            //{
            //    VolumeDelta = rightHandStatus.VolumeDelta;
            //    IsVolumeDeltaActive = true;
            //}
            if (leftHandStatus.Phase == HandConductingPhase.Volume)
            {
                VolumeDelta = MathUtility.Clamp(MathUtility.MapValue(leftHandStatus.VolumeDelta, -.45, .2, 0, 1), 0, 1);
                IsVolumeDeltaActive = true;
            }
            else if (rightHandStatus.Phase == HandConductingPhase.MarkingTime && 
                leftHandStatus.Phase == HandConductingPhase.MarkingTime)
            {
                Volume = MathUtility.MapValue(rightHandStatus.Energy, 0, .8, 0, 1);
                IsVolumeActive = true;
            }
            else
            {
                IsVolumeDeltaActive = false;
                IsVolumeActive = false;
            }
        }

        private void UpdateBeatsPerMinute()
        {
            if (rightHandStatus.Phase == HandConductingPhase.MarkingTime)
            {
                BeatsPerMinute = rightHandStatus.BeatsPerMinute;
                IsBeatingActive = true;
            }
            //else if (leftHandStatus.Phase == HandConductingPhase.MarkingTime)
            //{
            //    BeatsPerMinute = leftHandStatus.BeatsPerMinute;
            //    IsBeatingActive = true;
            //}
            else
            {
                IsBeatingActive = false;
                BeatsPerMinute = -1;
            }
        }

        private void UpdateIsConducting()
        {
            bool isEitherHandConducting = leftHandStatus.Phase != HandConductingPhase.NotConducting || rightHandStatus.Phase != HandConductingPhase.NotConducting;

            if (this.IsConducting != isEitherHandConducting)
            {
                isChanged = true;
            }
            this.IsConducting = isEitherHandConducting;
        }

        #endregion
    }
}
